package org.jasper.impl.repository;

import org.jasper.api.container.ReleaseId;
import org.jasper.api.module.KieModule;
import org.jasper.api.repository.KieRepository;
import org.jasper.impl.container.ReleaseIdImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.NavigableMap;
import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicReference;

/**
 * Created by OP038074 on 2018/7/11.
 */
public class KieRepositoryImpl implements KieRepository {
    private static final Logger log = LoggerFactory.getLogger(KieRepositoryImpl.class);

    private static final String DEFAULT_VERSION = "1.0.0-SNAPSHOT";
    private static final String DEFAULT_ARTIFACT = "artifact";
    private static final String DEFAULT_GROUP = "org.default";

    public static final KieRepository INSTANCE = new KieRepositoryImpl();
    private final KieModuleRepo kieModuleRepo;

    private final AtomicReference<ReleaseId> defaultGAV = new AtomicReference(new ReleaseIdImpl(DEFAULT_GROUP,
            DEFAULT_ARTIFACT,
            DEFAULT_VERSION));

    public KieRepositoryImpl() {
        kieModuleRepo = new KieModuleRepo();
    }

    public void addKieModule(KieModule kieModule) {
        kieModuleRepo.store(kieModule);
        log.info("KieModule was added: " + kieModule);
    }

    public ReleaseId getDefaultReleaseId() {
        return this.defaultGAV.get();
    }

    private static final Object PRESENT = new Object();

    static class KieModuleRepo {

        public static final String CACHE_GA_MAX_PROPERTY = "kie.repository.project.cache.size";
        static final int MAX_SIZE_GA_CACHE
                = Integer.parseInt(System.getProperty(CACHE_GA_MAX_PROPERTY, "100"));
        public static final String CACHE_VERSIONS_MAX_PROPERTY = "kie.repository.project.versions.cache.size";
        static final int MAX_SIZE_GA_VERSIONS_CACHE
                = Integer.parseInt(System.getProperty(CACHE_VERSIONS_MAX_PROPERTY, "10"));

        final Map<String, NavigableMap<ComparableVersion, KieModule>> kieModules
                = new LinkedHashMap<String, NavigableMap<ComparableVersion, KieModule>>(16, 0.75f, true) {
            @Override
            protected boolean removeEldestEntry( Map.Entry<String, NavigableMap<ComparableVersion, KieModule>> eldest) {
                return (size() > MAX_SIZE_GA_CACHE);
            }
        };

        final LinkedHashMap<ReleaseId, KieModule> oldKieModules = new LinkedHashMap<ReleaseId, KieModule>() {
            @Override
            protected boolean removeEldestEntry( Map.Entry<ReleaseId, KieModule> eldest ) {
                return size() > (MAX_SIZE_GA_CACHE*MAX_SIZE_GA_VERSIONS_CACHE);
            };

        };

        synchronized void store(KieModule kieModule) {
            ReleaseId releaseId = kieModule.getReleaseId();
            String ga = releaseId.getGroupId() + ":" + releaseId.getArtifactId();
            ComparableVersion comparableVersion = new ComparableVersion(releaseId.getVersion());

            NavigableMap<ComparableVersion, KieModule> artifactMap = kieModules.get(ga);
            if( artifactMap == null ) {
                artifactMap = createNewArtifactMap();
                kieModules.put(ga, artifactMap);
            }

            KieModule oldReleaseIdKieModule = oldKieModules.get(releaseId);
            // variable used in order to test race condition
            if (oldReleaseIdKieModule == null) {
                KieModule oldKieModule = artifactMap.get(comparableVersion);
                if (oldKieModule != null) {
                    oldKieModules.put( releaseId, oldKieModule );
                }
            }
            artifactMap.put( comparableVersion, kieModule );
        }

        private NavigableMap<ComparableVersion, KieModule> createNewArtifactMap() {
            NavigableMap<ComparableVersion, KieModule> newArtifactMap =
                    new TreeMap<ComparableVersion, KieModule>() {
                private final Map<ComparableVersion, KieModule> artifactMap = this;

                LinkedHashMap<ComparableVersion, Object> backingLRUMap =
                        new LinkedHashMap<ComparableVersion, Object>(16, 0.75f, true) {
                    @Override
                    protected boolean removeEldestEntry( Map.Entry<ComparableVersion, Object> eldest ) {
                        boolean remove = (size() > MAX_SIZE_GA_VERSIONS_CACHE);
                        if( remove ) {
                            artifactMap.remove(eldest.getKey());
                        }
                        return remove;
                    }
                };

                @Override
                public KieModule put( ComparableVersion key, KieModule value ) {
                    backingLRUMap.put(key, PRESENT);
                    return super.put(key, value);
                }
            };
            return newArtifactMap;
        }
    }

    public static class ComparableVersion implements Comparable<ComparableVersion> {
        private String value;

        private String canonical;

        private ListItem items;

        private interface Item {
            int INTEGER_ITEM = 0;
            int STRING_ITEM = 1;
            int LIST_ITEM = 2;
            int compareTo(Item item);
            int getType();
        }

        private static class ListItem extends ArrayList<Item> implements Item {
            public int getType() {
                return LIST_ITEM;
            }
            public int compareTo(Item item) {
                if (item == null) {
                    if (size() == 0) {
                        return 0; // 1-0 = 1- (normalize) = 1
                    }
                    Item first = get(0);
                    return first.compareTo(null);
                }
                switch (item.getType()) {
                    case INTEGER_ITEM:
                        return -1; // 1-1 < 1.0.x

                    case STRING_ITEM:
                        return 1; // 1-1 > 1-sp

                    case LIST_ITEM:
                        Iterator<Item> left = iterator();
                        Iterator<Item> right = ((ListItem) item).iterator();

                        while (left.hasNext() || right.hasNext()) {
                            Item l = left.hasNext() ? left.next() : null;
                            Item r = right.hasNext() ? right.next() : null;

                            // if this is shorter, then invert the compare and mul with -1
                            int result = l == null ? -1 * r.compareTo(l) : l.compareTo(r);

                            if (result != 0) {
                                return result;
                            }
                        }

                        return 0;

                    default:
                        throw new RuntimeException("invalid item: " + item.getClass());
                }
            }

            public String toString() {
                StringBuilder buffer = new StringBuilder("(");
                for (Iterator<Item> iter = iterator(); iter.hasNext();)
                {
                    buffer.append(iter.next());
                    if (iter.hasNext())
                    {
                        buffer.append(',');
                    }
                }
                buffer.append(')');
                return buffer.toString();
            }
        }

        public ComparableVersion(String version) {
            parseVersion(version);
        }

        public final void parseVersion(String version) {

        }

        @Override
        public int compareTo(ComparableVersion o) {
            return items.compareTo(o.items);
        }

        public String toString() {
            return value;
        }

        public boolean equals(Object o) {
            return (o instanceof ComparableVersion) && canonical.equals(((ComparableVersion) o).canonical);
        }

        public int hashCode() {
            return canonical.hashCode();
        }
    }
}
